﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows.Threading;
using Quail;
using QuailDashboard.View.Controls;
using QuailDashboard.View.Converters;
using QuailDashboard.View.Helpers;
using QuailDashboard.View.Models;
using QuailDashboard.View.QuailServiceReference;

namespace QuailDashboard.View.Data
{
    /// <summary>
    /// Wraps the TestRunTask data from the service call
    /// </summary>
    public class TestRunTaskData : Model, IHierarchalData
    {
        private static readonly StatusIdConverter StatusIdConverter = new StatusIdConverter();
        private static readonly List<TestRunTaskData> PendingPopulates = new List<TestRunTaskData>();
        private static int _populatesInQueue;
        private readonly ObservableCollection<TestRunTaskData> _items = new ObservableCollection<TestRunTaskData>();
        private readonly TestRunTaskData _parent;
        private readonly RunStatusResultWrapper _runresult;
        private readonly ObservableCollection<TestRunTaskLogEntry> _logEntries = new ObservableCollection<TestRunTaskLogEntry>();
        private readonly ObservableCollection<TestRunTaskAttribute> _attributes = new ObservableCollection<TestRunTaskAttribute>();
        private readonly TestRunTask _data;
        private ObservableCollection<TestRunTaskLogEntry> _logDelayLoadEntries;
        private StatusIdConverter.StringModel _taskStatus;

        private bool _itemsisok, _populatePending;

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunTaskData"/> class.
        /// </summary>
        /// <param name="runresult">The owning run result (may be null)</param>
        /// <param name="parent">The parent task.</param>
        /// <param name="task">The test run task.</param>
        public TestRunTaskData(RunStatusResultWrapper runresult, TestRunTaskData parent, TestRunTask task)
        {
            _runresult = runresult;
            _parent = parent;
            _data = task;

            CheckPendingStatus();
        }

        /// <summary>
        /// Fires when the Populate async method has completed
        /// </summary>
        public event EventHandler PopulateComplete;

        /// <summary>
        /// Gets the parent task
        /// </summary>
        public IHierarchalData Parent
        {
            get
            {
                return _parent;
            }
        }

        /// <summary>
        /// Gets the root task
        /// </summary>
        public TestRunTaskData Root
        {
            get
            {
                return _parent == null ? this : _parent.Root;
            }
        }

        /// <summary>
        /// Gets the key for the hierarchal data
        /// </summary>
        public long Key
        {
            get
            {
                return _data.TestRunTaskId;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the Items are populated
        /// </summary>
        public bool IsPopulated
        {
            get
            {
                return _itemsisok;
            }
        }

        /// <summary>
        /// Gets or sets the task finish date.
        /// </summary>
        /// <value>The task finish date.</value>
        public DateTime? TaskFinishDate
        {
            get
            {
                if (_data == null)
                    return null;

                return _data.TaskFinishDate;
            }

            set
            {
                if (_data == null)
                    return;

                if (value == null || !_data.TaskFinishDate.Equals(value))
                {
                    _data.TaskFinishDate = value;
                    NotifyPropertyChanged("TaskFinishDate");
                    NotifyPropertyChanged("Duration");
                }
            }
        }

        /// <summary>
        /// Gets or sets the task status id.
        /// </summary>
        /// <value>The task status id.</value>
        public int TaskStatusId
        {
            get
            {
                return _data.TaskStatusId;
            }
            set
            {
                if (value != _data.TaskStatusId)
                {
                    _data.TaskStatusId = value;
                    _taskStatus = null;
                    NotifyPropertyChanged("TaskStatus");
                    NotifyPropertyChanged("TaskStatusId");
                }
            }
        }

        /// <summary>
        /// Gets the elapsed time as a string
        /// </summary>
        public string Elapsed
        {
            get
            {
                if (_data == null)
                    return string.Empty;

                if (_parent == null)
                    return "0:00";

                return GetDuration(_data.TaskStartDate - TestStartTime);
            }
        }
        
        /// <summary>
        /// Gets the test start time
        /// </summary>
        public DateTime TestStartTime
        {
            get
            {
                return _parent == null ? _data.TaskStartDate : _parent.TestStartTime;
            }
        }

        /// <summary>
        /// Gets the duration of the task in hours:minutes:seconds
        /// </summary>
        public string Duration
        {
            get
            {
                if (_data == null)
                    return string.Empty;

                var finish = _data.TaskFinishDate;
                var start = _data.TaskStartDate;

                if (finish.HasValue)
                    return GetDuration(finish.Value - start);

                finish = DateTime.Now;
                if (finish.Value < start)
                    return "-:--:--";

                return GetDuration(finish.Value - start);

            }
        }

        /// <summary>
        /// Gets the task's status as a string
        /// </summary>
        public string TaskStatus
        {
            get
            {
                if (_taskStatus == null)
                {
                    _taskStatus = (StatusIdConverter.StringModel)StatusIdConverter.Convert(_data.TaskStatusId, typeof(StatusIdConverter.StringModel), null, CultureInfo.CurrentCulture);
                    _taskStatus.PropertyChanged += OnTaskStatusValueChanged;
                }

                return _taskStatus.Value;
            }
        }

        /// <summary>
        /// Gets the list of items
        /// </summary>
        public IList Items
        {
            get
            {
                return _items;
            }
        }

        /// <summary>
        /// Gets the list of items
        /// </summary>
        public ObservableCollection<TestRunTaskData> ChildTasks
        {
            get
            {
                return _items;
            }
        }

        /// <summary>
        /// Gets the list of items
        /// </summary>
        public ObservableCollection<TestRunTaskLogEntry> LogEntries
        {
            get
            {
                return _logEntries;
            }
        }

        /// <summary>
        /// Gets the list of items
        /// </summary>
        public ObservableCollection<TestRunTaskAttribute> Attributes
        {
            get
            {
                return _attributes;
            }
        }

        /// <summary>
        /// Gets the name of the task.
        /// </summary>
        /// <value>The name of the task.</value>
        public string TaskName
        {
            get
            {
                return _data != null ? _data.TaskName : string.Empty;
            }
        }

        /// <summary>
        /// Gets the task start date.
        /// </summary>
        /// <value>The task start date.</value>
        public DateTime TaskStartDate
        {
            get
            {
                return _data != null ? _data.TaskStartDate : new DateTime(1900, 1, 1);
            }
        }

        /// <summary>
        /// Gets the test run task id.
        /// </summary>
        /// <value>The test run task id.</value>
        public long TestRunTaskId
        {
            get
            {
                return _data != null ? _data.TestRunTaskId : 0;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is an active test run - actively selected by a view
        /// </summary>
        private bool IsSelectedTestRun
        {
            get
            {
                return TestRunLogsControl.IsSelectedTestRun(_data.TestRunId);
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is still worth looking for pending updates
        /// </summary>
        private bool IsYoungTest
        {
            get
            {
                return TestStartTime > DateTime.Now.AddHours(-5);
            }
        }

        /// <summary>
        /// Populates the sub-items of this task if it hasn't already been populated
        /// </summary>
        public void Populate()
        {
            Populate(false);
        }

        /// <summary>
        /// Populates the sub-items of this task
        /// </summary>
        /// <param name="refresh">true to force a refresh</param>
        public void Populate(bool refresh)
        {
            if (!_populatePending && (!_itemsisok || refresh))
            {
                lock (PendingPopulates)
                {
                    if (!IsSelectedTestRun)
                    {
                        PendingPopulates.Remove(this);

                        while (PendingPopulates.Count > 0)
                        {
                            var task = PendingPopulates[0];
                            if (!task.IsSelectedTestRun)
                                PendingPopulates.RemoveAt(0);
                            else
                            {
                                task.Populate(true);
                                break;
                            }
                        }
                    }
                    else if (_populatesInQueue < 5 * 3)
                    {
                        PendingPopulates.Remove(this);
                        _populatesInQueue += 3;
                        _populatePending = true;

                        var service = QuailDataServiceFactory.CreateClient();
                        service.GetTestRunTaskCompleted += Webservice_GetTestRunTaskCompleted;
                        service.GetTestRunTaskAsync(_data.TestRunId, _data.TestRunTaskId);

                        service.GetTestRunTaskLogEntryCompleted += Webservice_GetTestRunTaskLogEntryCompleted;
                        service.GetTestRunTaskLogEntryAsync(_data.TestRunTaskId);

                        service.GetTestRunTaskAttributeCompleted += Webservice_GetTestRunTaskAttributeCompleted;
                        service.GetTestRunTaskAttributeAsync(_data.TestRunTaskId);
                    }
                    else
                    {
                        // still need to wait
                        if (!PendingPopulates.Contains(this))
                            PendingPopulates.Add(this);
                    }
                }
            }
        }


        /// <summary>
        /// Makes a list of the tasks
        /// </summary>
        /// <param name="runResult">the owning runresult</param>
        /// <param name="parent">The parent task</param>
        /// <param name="tasks">the tasks to add</param>
        /// <returns>a list of the tasks</returns>
        public static ObservableCollection<TestRunTaskData> MakeList(RunStatusResultWrapper runResult, TestRunTaskData parent, ObservableCollection<TestRunTask> tasks)
        {
            var newitems = new ObservableCollection<TestRunTaskData>();

            foreach (var item in tasks)
            {
                // skip rolled-back items
                if (item.TaskStatusId != (int) LogStatus.Rollback)
                    newitems.Add(new TestRunTaskData(runResult, parent, item));
            }

            return newitems;
        }

        private void OnTaskStatusValueChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChanged("TaskStatus");
        }

        private void Webservice_GetTestRunTaskCompleted(object sender, GetTestRunTaskCompletedEventArgs e)
        {
            _populatePending = false;

            CheckPopulateQueue();

            if (!IsSelectedTestRun)
                return;

            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting task data.", e.Error);
                return;
            }

            var myitems = new Dictionary<long, TestRunTaskData>();

            foreach (var item in _items)
            {
                myitems[item.TestRunTaskId] = item;
            }

            var pollforchanges = (_data.TaskStatusId == (int) LogStatus.AsyncPending) || (_data.TaskStatusId == (int) LogStatus.Pending);

            foreach (var item in MakeList(null, this, e.Result))
            {
                var key = item.TestRunTaskId;

                if (!pollforchanges && ((item._data.TaskStatusId == (int)LogStatus.Pending) || (item._data.TaskStatusId == (int)LogStatus.AsyncPending)))
                    pollforchanges = true;

                if (myitems.ContainsKey(key))
                {
                    var existingitem = myitems[key];
                    existingitem.TaskFinishDate = item.TaskFinishDate;
                    existingitem.TaskStatusId = item.TaskStatusId;
                }
                else
                    _items.Add(item);
            }

            _itemsisok = true;

            if (PopulateComplete != null)
                PopulateComplete(this, EventArgs.Empty);

            if (pollforchanges && IsYoungTest)
            {
                // create a 5 second timer
                var timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 5) };
                timer.Tick += OnPopulateTimerTick;
                timer.Start();
            }
        }

        private void OnPopulateTimerTick(object sender, EventArgs e)
        {
            var timer = sender as DispatcherTimer;
            if (timer != null)
                timer.Stop();

            if (!IsSelectedTestRun)
                return;

            Populate(true);
        }

        private void CheckPendingStatus()
        {
            if (_data.ParentTestRunTaskId == 0 && IsYoungTest &&
                (_data.TaskStatusId == (int)LogStatus.AsyncPending || _data.TaskStatusId == (int)LogStatus.Pending))
            {
                // create a 10 second timer
                SetTimer(new TimeSpan(0,0,0,10), t => t.Tick += OnRefreshMeTimerTick);                
            }
        }

        private void SetTimer(TimeSpan interval, Action<DispatcherTimer> func)
        {
            var timer = new DispatcherTimer {Interval = interval};
            func(timer);
            timer.Start();
        }

        private void OnRefreshMeTimerTick(object sender, EventArgs e)
        {
            var timer = sender as DispatcherTimer;
            if (timer != null)
                timer.Stop();

            if (!IsSelectedTestRun)
                return;

            var service = QuailDataServiceFactory.CreateClient();
            service.GetTestRunTaskDataCompleted += Webservice_GetTestRunTaskDataCompleted;
            service.GetTestRunTaskDataAsync(_data.TestRunTaskId);
        }

        private static void CheckPopulateQueue()
        {
            lock (PendingPopulates)
            {
                _populatesInQueue--;
                if (PendingPopulates.Count > 0)
                    PendingPopulates[0].Populate(true);
            }
        }

        private void Webservice_GetTestRunTaskDataCompleted(object sender, GetTestRunTaskDataCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                App.CurrentApp.ShowError(e.Error);
                return;
            }

            var data = e.Result;

            if (data == null || !IsSelectedTestRun)
                return;

            TaskFinishDate = data.TaskFinishDate;
            TaskStatusId = data.TaskStatusId;

            if (_runresult != null)
            {
                // reset the parent runresult?
                _runresult.RunStatusId = data.TaskStatusId;
                _runresult.RunStatus = ((StatusIdConverter.StringModel)new StatusIdConverter().Convert(data.TaskStatusId, typeof(StatusIdConverter.StringModel), null, null)).Value;

                var finish = data.TaskFinishDate.HasValue ? data.TaskFinishDate.Value : DateTime.Now;

                _runresult.Duration = new DateTime((finish - data.TaskStartDate).Ticks);
            }

            CheckPendingStatus();
        }

        private void Webservice_GetTestRunTaskAttributeCompleted(object sender, GetTestRunTaskAttributeCompletedEventArgs e)
        {
            CheckPopulateQueue();

            if (!IsSelectedTestRun)
                return;

            if (e.Error != null)
                App.CurrentApp.ShowError("Error during GetTestRunTaskAttribute", e.Error);
            else
            {
                foreach (var item in e.Result)
                {
                    var i = item;
                    if (!_attributes.Any(att => i.TestRunTaskAttributeId == att.TestRunTaskAttributeId))
                    {
                        _attributes.Add(item);
                    }
                }
            }
        }

        private void Webservice_GetTestRunTaskLogEntryCompleted(object sender, GetTestRunTaskLogEntryCompletedEventArgs e)
        {
            CheckPopulateQueue();

            if (!IsSelectedTestRun)
                return;

            if (e.Error != null)
                App.CurrentApp.ShowError("Error during GetTestRunTaskLogEntry", e.Error);
            else
            {
                foreach (var item in e.Result)
                {
                    var i = item;
                    if (!_logEntries.Any(le => i.TestRunTaskLogEntryId == le.TestRunTaskLogEntryId))
                    {
                        _logEntries.Add(item);
                    }
                }
            }
        }

        private static string GetDuration(TimeSpan span)
        {
            var result = string.Empty;

            if (span.TotalHours >= 1)
                result += string.Format("{0:0}:", Math.Floor(span.TotalHours));

            result += string.Format("{0:00}:{1:00}", span.Minutes, span.Seconds);

            return result;            
        }
    }
}
